package com.bluemoon.bmwalk.common.jacoco;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.List;

import org.jacoco.cli.internal.core.analysis.Analyzer;
import org.jacoco.cli.internal.core.analysis.CoverageBuilder;
import org.jacoco.cli.internal.core.analysis.IBundleCoverage;
import org.jacoco.cli.internal.core.tools.ExecDumpClient;
import org.jacoco.cli.internal.core.tools.ExecFileLoader;
import org.jacoco.cli.internal.report.DirectorySourceFileLocator;
import org.jacoco.cli.internal.report.FileMultiReportOutput;
import org.jacoco.cli.internal.report.IReportVisitor;
import org.jacoco.cli.internal.report.ISourceFileLocator;
import org.jacoco.cli.internal.report.MultiSourceFileLocator;
import org.jacoco.cli.internal.report.html.HTMLFormatter;
import org.jacoco.cli.internal.report.xml.XMLFormatter;

import com.bluemoon.bmwalk.entity.CodePATH;

import lombok.extern.slf4j.Slf4j;

/**
 * @program: jacoco基础操作.
 * @description: jacoco基础操作.
 * @author: liaozhioyng
 * @create: 2023-09-13
 */
@Slf4j
public class CommandBase {
	
	/**
	 * 执行dump命令
	 * @param address
	 * @param port
	 * @param reset 是否重置
	 * @param execfile
	 * @return
	 */
	public static int executeDump(String address, Short port, boolean reset, File execfile){
		int flag = 1;
		try {
			ExecDumpClient client = new ExecDumpClient() {
				protected void onConnecting(final InetAddress address,final int port) {
					System.out.println(String.format("[INFO] Connecting to %s:%s.%n", address, Integer.valueOf(port)));
				}

				protected void onConnectionFailure(final IOException exception) {
					System.out.println(String.format("[WARN] %s.%n", exception.getMessage()));
				}
			};
			client.setReset(reset);
			client.setRetryCount(10);

			final ExecFileLoader loader = client.dump(address, port);
			System.out.println(String.format("[INFO] Writing execution data to %s.%n",execfile.getAbsolutePath()));
			loader.save(execfile, true);
			flag = 0;
		} catch (IOException e) {
			e.printStackTrace();
			flag = 1;
		}
		return flag;
	}

	/**
	 * 执行生成报告
	 * @param execFile exec路径
	 * @param reportDir 报告路径
	 * @param project 项目名
	 * @param diffCode 差异代码
	 */
	public static int executeReport(CodePATH codePATH,
			String execFile,
			String reportDir, String project, String diffCode){
		int flag = 1;
		try {
//			// 目录下必须包含源码编译过的class文件,用来统计覆盖率。所以这里用server打出的jar包地址即可,运行的jar或者Class目录
//			File classesDirectory = new File(projectDirectory, classesDir); // "target\\classes"
//			File sourceDirectory = new File(projectDirectory, sourceDir); // "src\\main\\java"
//			List<File> modules = new ArrayList<>();
//			List<File> classesDirectories = new ArrayList<>();
//			classesDirectories.add(classesDirectory);
//			modules.add(sourceDirectory);
			// 覆盖率的exec文件地址
			File executionDataFile = new File(execFile);
			// 要保存报告的地址
			File reportDirectory = new File(reportDir, project);
			CommandBase coverApiTest = new CommandBase();
			// 解析exec
			ExecFileLoader execFileLoader = coverApiTest.loadExecutionData(executionDataFile);
			// 对比exec和class类，生成覆盖数据
			IBundleCoverage bundleCoverage = coverApiTest.analyzeStructure(execFileLoader, codePATH.getClassesDirectories(), diffCode);
			// 生成报告
			coverApiTest.createReport(bundleCoverage, execFileLoader, reportDirectory, codePATH.getSources());
			flag = 0;
		} catch (IOException e) {
			e.printStackTrace();
			flag = 1;
		}
		return flag;
	}
	
	/**
	 * 第一步解析exec文件
	 *
	 * @throws IOException
	 */
	public ExecFileLoader loadExecutionData(File executionDataFile)
			throws IOException {
		// 解析exec
		ExecFileLoader execFileLoader = new ExecFileLoader();
		execFileLoader.load(executionDataFile);
		return execFileLoader;
	}

	public IBundleCoverage analyzeStructure(ExecFileLoader execFileLoader, List<File> classesDirectories, String diffCode)
			throws IOException {

		// 增量覆盖
		// final CoverageBuilder coverageBuilder = new
		// CoverageBuilder("{method:"add"}");
		System.out.println("classesDirectories"+classesDirectories);
		CoverageBuilder	 coverageBuilder = new CoverageBuilder(diffCode, null);
		final Analyzer analyzer = new Analyzer(
				execFileLoader.getExecutionDataStore(), coverageBuilder);
		for (File classesDirectory : classesDirectories) {
			analyzer.analyzeAll(classesDirectory);
		}
//		analyzer.analyzeAll(classesDirectory);
		// new BundleCoverageImpl()
		return coverageBuilder.getBundle("报告率");
	}

	public void createReport(final IBundleCoverage bundleCoverage, ExecFileLoader execFileLoader, File reportDirectory,  List<File> sources) throws IOException {

		final HTMLFormatter htmlFormatter = new HTMLFormatter();
		final IReportVisitor visitor = htmlFormatter
				.createVisitor(new FileMultiReportOutput(reportDirectory));

		// 设置源代码文件定位器
		ISourceFileLocator sourceFileLocator = new MultiSourceFileLocator(4);
//		 向源代码文件定位器添加每个模块的源代码目录
		addSourceDirs(sourceFileLocator,sources);
		visitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(),
				execFileLoader.getExecutionDataStore().getContents());
		visitor.visitBundle(bundleCoverage, sourceFileLocator,null);
		visitor.visitEnd();

		File reportFile = new File(reportDirectory, "index.xml");
		reportFile.getParentFile().mkdirs(); // 确保目标目录存在
		final XMLFormatter xmlFormatter = new XMLFormatter();

		try (OutputStream outputStream = new FileOutputStream(reportFile)) {
			final IReportVisitor xmlVisitor = xmlFormatter.createVisitor(outputStream);
			xmlVisitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(),
					execFileLoader.getExecutionDataStore().getContents());
			xmlVisitor.visitBundle(bundleCoverage, sourceFileLocator,null);
			xmlVisitor.visitEnd();
		} catch (IOException e) {
			log.error("找不到xml目录");
		}
	}
	
	/**
	 * maven多模块源代码添加到jacoco生成报告中
	 * @param sourceFileLocator
	 * @param sources
	 */
	private static void addSourceDirs(ISourceFileLocator sourceFileLocator, List<File> sources) {
		// 分别将每个模块的源代码目录添加到源代码文件定位器中
		MultiSourceFileLocator multiSourceLocator = (MultiSourceFileLocator) sourceFileLocator;
		for (File source : sources) {
			multiSourceLocator.add(new DirectorySourceFileLocator(source, "UTF-8", 4));
		}
	}

}
